#include "WProgram.h"
#include <EEPROM.h>

#include <OneWire.h>
#include <DallasTemperature.h>
#include <Flash.h>
#include <Time.h>

#include <AnalogButton.h>
#include <Dogm.h>

//#include "SystemFont5x7.h"   // system font
#include "main_image.h"
#include "consts.h"

extern int  __bss_end;
extern void *__brkval;

//Dog128 GLCD(11,13,12,10,9,6);
Dogm dogm(12,10);
OneWire oneWire(3);  // on pin 3
DallasTemperature tempSensors(&oneWire);

byte backlight = 200;
byte currentBacklight = 0;
byte requestedBacklight = 0;
byte alarmbacklight = 0;
byte contrast = 12;

byte state = STATE_MAIN;
byte alarm = ALARM_OFF;
byte menu_pos = MENU_MAIN;
byte device_pos = 0;
byte device_row = 0;
byte sensor_pos = 0;
byte sensor_row = 0;
byte time_pos = 0;
byte preferences_setup_pos = 0;
byte preferenecs_setup_edit = 0;
byte history_pos = 0;
byte history_setup_pos = 0;
byte history_setup_edit = 0;
byte data_select_pos = 0;
byte data_selected = 0;
unsigned long lastButtonMillis = 0;
float lastTemp[NUMBER_OF_DEVICES];
float lastVariable[NUMBER_OF_VARIABLES];
byte lastMinDevice = 0;
time_t lastMinTempTime = 0;
time_t tankEmptyTime = 0;
byte fireState = false;
time_t fireStateTime = 0;
float breakTemp = 50.0;
float loadHistory[NUMBER_OF_LOAD_HISTORY];
time_t lastLoadHistory = 0;
byte sound = 0;
byte lastAlarm = 80;

//byte counter = 0;

int lastButton = 0;
int buttonValues[] = {30, 200, 400};

struct historySettingsStruct {
  byte data;
  byte resolution;
  char max;
  char min;
} historySettings[NUMBER_OF_HISTORY];

byte lastHistoryPos[NUMBER_OF_HISTORY];
time_t lastHistoryTime[NUMBER_OF_HISTORY];
byte history[TOTAL_HISTORY];

/**
 * Callback routine with a pressed button.
 */
void buttonReceived(byte button) {
  lastButton = button + 1;
  lastButtonMillis = millis();
}

void updateEeprom(int address, byte value) {
  byte tmp = EEPROM.read(EEPROM_BACKLIGHT_OFFSET);
  if(tmp != value) {
    EEPROM.write(address, value);
  }
}

//int get_free_memory()
//{
//  int free_memory;
//
//  if((int)__brkval == 0)
//    free_memory = ((int)&free_memory) - ((int)&__bss_end);
//  else
//    free_memory = ((int)&free_memory) - ((int)__brkval);
//
//  return free_memory;
//}

/**
 * Goto main state.
 */
void goto_main() {
  state = STATE_MAIN;
  //XXX dogm.start();
  //XXX do {
  //XXX   dogm.setBitmapP(0,0,mainImage2, 128, 64);
  //XXX } while(dogm.next());
}

/**
 * Goto a state.
 */
void goto_state(byte to_state) {
  state = to_state;
}

/**
 * Get a sensor that is set to a device.
 */
byte get_device_sensor(byte device, byte sensor[]) {
  word offset = device*EEPROM_SENSOR_SIZE + EEPROM_SENSOR_OFFSET;
  byte result = EEPROM.read(offset); //Read if sensor is set

  for(int i=0; i<8; i++) {
    sensor[i] = EEPROM.read(offset+i+1);
  }

  return result;
}

/**
 * Sets a sensor to a device.
 */
void set_device_sensor(byte device, byte sensor[]) {
  word offset = device*EEPROM_SENSOR_SIZE + EEPROM_SENSOR_OFFSET;
  EEPROM.write(offset, 1); //Write that sensor is set

  for(int i=0; i<8; i++) {
    EEPROM.write(offset+i+1, sensor[i]);
  }
}

/**
 * Clear a device from the set sendor.
 */
void clear_device_sensor(byte device) {
  word offset = device*EEPROM_SENSOR_SIZE;
  EEPROM.write(offset, 0); //Write that sensor is not set

  for(int i=0; i<8; i++) {
    EEPROM.write(offset+i+1, 0xFF);
  }
}

/**
 * Set the backlight of the display.
 * @param value the new value for the backlight. If the
 * backlight driver circuit is connected to a normal digital IO-pin
 * on the arduino using value=LOW or value=HIGH will switch
 * off/on as expected.
 * @param usePWM set this to true if the driver is connected to
 * a PWM pin on the arduino you can set any dim the backlight
 * with values from 0..255
 */
void setBacklight(int value, bool usePWM=false) {
  if(!usePWM) {
    if(value==LOW) {
      digitalWrite(BACKLIGHT_PIN,LOW);
    } else {
      digitalWrite(BACKLIGHT_PIN,HIGH);
    }
  } else {
    if(value>255)
      value=255;
    analogWrite(BACKLIGHT_PIN,value);
  }
}

/**
 * Reset the display hardware.
 */
void reset(bool swReset=false) {
  if(!swReset) {
    //If user wired the reset line, pull it low and wait for 40 millis
    digitalWrite(RESET_PIN,LOW);
    delay(40);
    digitalWrite(RESET_PIN,HIGH);
    delay(40);
  } else {
    //User wants software reset, we simply wait a bit for stable power
    delay(50);
  }
  //XXX Init(NON_INVERTED);
}

/**
 * Goto cursor position.
 */
void CursorTo(byte x, byte y) {
  dogm.setXY(x*CHAR_WIDTH, 63 - (y + 1)*CHAR_HEIGHT);
}

/**
 * Print a number with possibility to fill with zeros and inverse text.
 * If fill > 100 use space instead of 0 and do fill-100.
 */
void PrintNumber(int num, byte inverse=0, byte fill=0, byte sign=0, byte base=DEC) {
  byte tx = dogm.tx;
  byte ty = dogm.ty;

  if(fill > 100) {
    fill = fill - 100;
    if(num < 0 && sign){
      while((fill > 1) && ((-num % ((int) pow(10,fill-2))) == -num)) {
        dogm.print(' ');
        fill--;
      }
    } else {
      while((fill > 1) && ((num % ((int) pow(10,fill-1))) == num)) {
        dogm.print(' ');
        fill--;
      }
    }
  } else if(fill > 0) {
    if(num < 0 && sign) {
      num = -num;
      dogm.print('-');
      fill--;
    }
    while((fill > 1) && ((num % ((int) pow(10,fill-1))) == num)) {
      dogm.print('0');
      fill--;
    }
  }

  dogm.print(num, base);

  if(inverse) {
    dogm.xorBox(tx,ty<0||ty>127 ? 0 : ty,dogm.tx-1,ty+CHAR_HEIGHT);
  }
}

/**
 * Prints a string from the flash memory with the possibility to inverse the text.
 * Only supports one line strings.
 */
void PrintString(PGM_P str, byte inverse=0) {
  byte tx = dogm.tx;
  byte ty = dogm.ty;

  while(pgm_read_byte(str) != 0) {
    dogm.print(pgm_read_byte(str));
    str++;
  }

  if(inverse) {
    //dogm.xorBox(tx,ty,min(dogm.tx,127),min(ty+CHAR_HEIGHT,63));
    dogm.xorBox(tx, ty<0||ty>127 ? 0 : ty, dogm.tx-1, ty+CHAR_HEIGHT);
  }
}

/**
 * Print a character with the possibility to inverse the text.
 */
void PrintChar(char c, byte inverse=0) {
  byte tx = dogm.tx;
  byte ty = dogm.ty;

  dogm.print(c);

  if(inverse) {
    dogm.xorBox(tx,ty<0||ty>127 ? 0 : ty,dogm.tx-1,ty+CHAR_HEIGHT);
  }
}

/**
 * The device setup state. This is where the IDs from the 1-wire devices
 * are linked to the displayed or controlled data.
 */
void state_device_setup() {
  //XXX static float lastTemp[NUMBER_OF_DEVICES];

  bool lastRequestDone = tempSensors.lastRequestDone();

  byte sensor[8];
  dogm.start();
  do {
    byte row = device_row;
    for(byte i=0; i<MAX_DISPLAY_ROWS; i++) {
      CursorTo(0,i);
      if(row == 0) {
        PrintString(STRING_BACK, 0 == device_pos);
      } else if(row <= NUMBER_OF_DEVICES) {
        PrintString((char*)pgm_read_word(&(DEVICE_STRING_TABLE[row-1])), row == device_pos);
        PrintChar(':', row == device_pos);

        get_device_sensor(row-1, sensor);
        for(int j=3; j>0; j--) {
          PrintNumber(sensor[j], row == device_pos, 2, 0, HEX);
        }

        //Print out temperature
        if(sensor[0] == 0x10) {
          if(lastRequestDone) {
            lastTemp[row-1] = (byte) tempSensors.getTempC(sensor);
          }
          PrintChar(':', row == device_pos);
          PrintNumber(lastTemp[row-1], row == device_pos, 2);
        }
      }
      row++;
    }
  } while(dogm.next());

  if(lastRequestDone) {
    tempSensors.requestTemperatures(true);
  }

  switch(lastButton) {
  case BUTTON_MENU:
    if(device_pos == 0) {
      goto_state(STATE_MENU);
    } else {
      goto_state(STATE_SENSOR_SELECT);
    }
    break;
  case BUTTON_UP:
    if(device_pos > 0) device_pos -= 1;
    if(device_pos < device_row) {
      device_row = device_pos;
    }
    break;
  case BUTTON_DOWN:
    if(device_pos < (NUMBER_OF_DEVICES)) device_pos += 1;
    if(device_pos > (device_row + MAX_DISPLAY_ROWS - 1)) {
      device_row = device_pos - MAX_DISPLAY_ROWS + 1;
    }
    break;
  }
  lastButton = 0;
}

/**
 * The sensor select state. This is where a sensor is selected from a list
 * of currently existing sensors on the 1-wire network.
 */
void state_sensor_select() {
  static byte lastTemp[MAX_SENSORS];

  byte nr_sensors = tempSensors.getDeviceCount();

  if(sensor_pos > nr_sensors+1) {
    sensor_pos = nr_sensors+1;
  }

  bool lastRequestDone = tempSensors.lastRequestDone();

  byte sensor[8];
  dogm.start();
  do {
    byte row = sensor_row;
    for(byte i=0; i<MAX_DISPLAY_ROWS; i++) {
      CursorTo(0,i);
      if(row == 0) {
        PrintString(STRING_BACK, 0 == sensor_pos);
      } else if(row == nr_sensors+1) {
        PrintString(STRING_CLEAR, nr_sensors+1 == sensor_pos);
      } else if(row <= nr_sensors) {
        tempSensors.getAddress(sensor, row-1);

        //Print out device type
        if(sensor[0] == 0x10) {
          PrintChar('T', row == sensor_pos);
        } else {
          PrintChar('U', row == sensor_pos);
        }

        PrintNumber(row-1, row == sensor_pos, 2);
        PrintChar(':', row == sensor_pos);

        //Print out device id
        for(int j=6; j>0; j--) {
          PrintNumber(sensor[j], row == sensor_pos, 2, 0, HEX);
        }

        //Print out temperature
        if(sensor[0] == 0x10) {
          if(lastRequestDone) {
            lastTemp[row-1] = (byte) tempSensors.getTempC(sensor);
          }
          PrintChar(':', row == sensor_pos);
          PrintNumber(lastTemp[row-1], row == sensor_pos, 2, 0);
        }
      }
      row++;
    }
  } while(dogm.next());

  if(lastRequestDone) {
    tempSensors.requestTemperatures(true);
  }

  switch(lastButton) {
  case BUTTON_MENU:
    if(sensor_pos == nr_sensors+1) {
      clear_device_sensor(device_pos-1);
    } else if(sensor_pos > 0) {
      tempSensors.getAddress(sensor, sensor_pos-1);
      set_device_sensor(device_pos-1, sensor);
    }
    goto_state(STATE_DEVICE_SETUP);
    break;
  case BUTTON_UP:
    if(sensor_pos > 0) sensor_pos -= 1;
    if(sensor_pos < sensor_row) {
      sensor_row = sensor_pos;
    }
    break;
  case BUTTON_DOWN:
    if(sensor_pos < (nr_sensors+1))  sensor_pos += 1;
    if(sensor_pos > (sensor_row + MAX_DISPLAY_ROWS - 1)) {
      sensor_row = sensor_pos - MAX_DISPLAY_ROWS + 1;
    }
    break;
  }
  lastButton = 0;
}

/**
 * Checks the events that is common for the main states
 */
void check_events() {
  if(millis() < lastButtonMillis) {
    lastButtonMillis = 0;
  }

  if(lastTemp[DEVICE_LADDNING_UT] > 80) {
    if(fireState == false) {
      fireStateTime = now();
    }
    lastAlarm = 81;
    fireState = true;
  }

  if(lastTemp[DEVICE_LADDNING_UT] < 75) {
    fireState = false;
  }

  if(lastButton or
      /*(lastTemp[DEVICE_TANK1]<breakTemp and lastTemp[DEVICE_VARME_UT] > breakTemp) or*/
      (lastTemp[DEVICE_LADDNING_UT]<80 and fireState == true)) {
    requestedBacklight = backlight;
  } else if(millis() - lastButtonMillis > FADEOUT_MILLIS) {
    requestedBacklight = 0;
  }

  if(sound and
     lastTemp[DEVICE_LADDNING_UT]<78 and
     fireState == true and
     lastAlarm > 78 and
     lastVariable[VARIABLE_LOAD] < 90) {
    lastAlarm = 78;
    digitalWrite(BUZZER_PIN,HIGH);
    delay(1000);
    digitalWrite(BUZZER_PIN,LOW);
  }
}

/**
 * The main state where the current status is displayed.
 */
void state_main() {

  dogm.start();
  do {
    dogm.setBitmapP(0,63,mainImage2, 128, 64);
//    dogm.setHLine(73, 95, 56);
//    dogm.setHLine(73, 95, 57);
//    dogm.setHLine(74, 94, 58);
//    dogm.setHLine(74, 94, 59);
//    dogm.setHLine(75, 93, 60);
//    dogm.setHLine(77, 91, 61);

    for(byte i=0; i<NUMBER_TEMP_TABLE.rows(); i++) {
      if(NUMBER_TEMP_TABLE[i][5] == MAIN_TYPE_TEXT_CELCIUS ||
          NUMBER_TEMP_TABLE[i][5] == MAIN_TYPE_TEXT_CELCIUS_WITH_NEGATIVE) {
        dogm.setXY(NUMBER_TEMP_TABLE[i][1], NUMBER_TEMP_TABLE[i][2]);
        if(lastTemp[NUMBER_TEMP_TABLE[i][0]] == ILLEGAL_TEMP) {
          for(byte j=0; j<NUMBER_TEMP_TABLE[i][6] % 100; j++) {
            dogm.print('-');
          }
        } else {
          PrintNumber(lastTemp[NUMBER_TEMP_TABLE[i][0]], 0, NUMBER_TEMP_TABLE[i][6],
              NUMBER_TEMP_TABLE[i][5] == MAIN_TYPE_TEXT_CELCIUS_WITH_NEGATIVE);
        }
      } else if(NUMBER_TEMP_TABLE[i][5] == MAIN_TYPE_BOX_BLACK_WHEN_TEMP_OVER) {
        if(lastTemp[NUMBER_TEMP_TABLE[i][0]] >= breakTemp) {
          dogm.setBox(
              NUMBER_TEMP_TABLE[i][1],
              NUMBER_TEMP_TABLE[i][2],
              NUMBER_TEMP_TABLE[i][3]+NUMBER_TEMP_TABLE[i][1],
              NUMBER_TEMP_TABLE[i][4]+NUMBER_TEMP_TABLE[i][2]);
        }
      } else if(NUMBER_TEMP_TABLE[i][5] == MAIN_TYPE_TEXT_VARIABLE) {
        dogm.setXY(NUMBER_TEMP_TABLE[i][1], NUMBER_TEMP_TABLE[i][2]);
        PrintNumber(lastVariable[NUMBER_TEMP_TABLE[i][0] - VARIABLE_ID_START], 0, NUMBER_TEMP_TABLE[i][6], 0);
      } else if(NUMBER_TEMP_TABLE[i][5] == MAIN_TYPE_INVERSE_SCALE) {
        float value;
        if(NUMBER_TEMP_TABLE[i][0] >= VARIABLE_ID_START) {
          value = lastVariable[NUMBER_TEMP_TABLE[i][0] - VARIABLE_ID_START];
        } else {
          value = lastTemp[NUMBER_TEMP_TABLE[i][0]];
        }
        if(value > 0) {
          byte height = (value / (float) NUMBER_TEMP_TABLE[i][6]) * (float) NUMBER_TEMP_TABLE[i][4];
          dogm.setBox(
              NUMBER_TEMP_TABLE[i][1],
              NUMBER_TEMP_TABLE[i][2] + NUMBER_TEMP_TABLE[i][4] - height,
              NUMBER_TEMP_TABLE[i][1] + NUMBER_TEMP_TABLE[i][3],
              NUMBER_TEMP_TABLE[i][2] + NUMBER_TEMP_TABLE[i][4]);
        }
      }
    }
    dogm.setXY(6,20);
    if(fireState == true) {
      PrintString(STRING_ELDAT);
      dogm.setXY(6,12);
      time_t t = now() - fireStateTime;
      PrintNumber(numberOfHours(t), 0, 2);
      dogm.print(':');
      PrintNumber(numberOfMinutes(t), 0, 2);
      dogm.print(':');
      PrintNumber(numberOfSeconds(t), 0, 2);
    } else if(tankEmptyTime > now()) {
      PrintString(STRING_SLUT_OM);
      dogm.setXY(6,12);
      time_t t = tankEmptyTime - now();
      PrintNumber(numberOfHours(t), 0, 2);
      dogm.print(':');
      PrintNumber(numberOfMinutes(t), 0, 2);
    }

    //Print the time
    dogm.setXY(103,63-7);
    PrintNumber(hour(), 0, 2);
    dogm.setXY(117,63-7);
    PrintNumber(minute(), 0, 2);

    if(second() % 2 == 0) {
      dogm.setPixel(115, 63-2);
      dogm.setPixel(115, 63-4);
    }

    //counter = counter == 99 ? 0 : counter + 1;

  } while(dogm.next());

  /*if(lastTemp[DEVICE_INNE] > 30) {
    alarm = ALARM_DISPLAY;
  } else {
    alarmbacklight = backlight/2;
    alarm = ALARM_OFF;
  }*/
  /*
  if(alarm == ALARM_DISPLAY) {
    setBacklight((alarmbacklight - (alarmbacklight>127)*(alarmbacklight-127)*2)*2, true);
    alarmbacklight += 4;
    //delay(5);
  } else {
    setBacklight(backlight, true);
  }
   */

  check_events();

  if(currentBacklight == requestedBacklight) {
    switch(lastButton) {
    case BUTTON_MENU:
      goto_state(STATE_MENU);
      break;
    case BUTTON_UP:
      history_pos = NUMBER_OF_HISTORY-1;
      goto_state(STATE_HISTORY_VIEW);
      break;
    case BUTTON_DOWN:
      goto_state(STATE_TABLE_VIEW);
      break;
    }
  }
  lastButton = 0;
}

/**
 * The menu where different setup or status are selected.
 */
void state_menu() {
  dogm.start();
  do {
    CursorTo(0,0);
    PrintString(STRING_MAIN, menu_pos == MENU_MAIN);
    CursorTo(0,1);
    PrintString(STRING_HISTORY_SETUP, menu_pos == MENU_HISTORY_SETUP);
    CursorTo(0,2);
    PrintString(STRING_PREFERENCES_SETUP, menu_pos == MENU_PREFERENCES_SETUP);
    CursorTo(0,3);
    PrintString(STRING_TIME_SET, menu_pos == MENU_TIME_SET);
    CursorTo(0,4);
    PrintString(STRING_DEVICE_SETUP, menu_pos == MENU_DEVICE_SETUP);
  } while(dogm.next());

  switch(lastButton) {
  case BUTTON_MENU:
    switch(menu_pos) {
    case MENU_MAIN:
      goto_main();
      break;
    case MENU_HISTORY_SETUP:
      goto_state(STATE_HISTORY_SETUP);
      break;
    case MENU_PREFERENCES_SETUP:
      goto_state(STATE_PREFERENCES_SETUP);
      break;
    case MENU_TIME_SET:
      goto_state(STATE_TIME_SET);
      break;
    case MENU_DEVICE_SETUP:
      goto_state(STATE_DEVICE_SETUP);
      break;
    }
    break;
    case BUTTON_UP:
      if(menu_pos > 0) menu_pos -= 1;
      break;
    case BUTTON_DOWN:
      if(menu_pos < NUMBER_OF_MENUS-1) menu_pos += 1;
      break;
  }
  lastButton = 0;
}

/**
 * The time set state where the current time and date are set.
 */
void state_time_set() {
  int h = hour();
  int m = minute();
  int s = second();
  int d = day();
  int mt = month();
  int y = year();

  dogm.start();
  do {
    CursorTo(0,0);
    PrintNumber(h,  time_pos == 0, 2);
    dogm.print(':');
    PrintNumber(m,  time_pos == 1, 2);
    dogm.print(':');
    PrintNumber(s,  time_pos == 2, 2);
    dogm.print(' ');
    PrintNumber(y,  time_pos == 3, 4);
    dogm.print('-');
    PrintNumber(mt, time_pos == 4, 2);
    dogm.print('-');
    PrintNumber(d,  time_pos == 5, 2);
  } while(dogm.next());


  switch(lastButton) {
  case BUTTON_MENU:
    goto_state(STATE_MENU);
    break;
  case BUTTON_UP:
    time_pos = (time_pos + 1) % 6;
    break;
  case BUTTON_DOWN:
    if (time_pos == 0) h = (h + 1) % 24;
    if (time_pos == 1) m = (m + 1) % 60;
    if (time_pos == 2) s = (s + 1) % 60;
    if (time_pos == 3) y = y + 1;
    if (time_pos == 4) mt = (mt + 1) % 12;
    if (time_pos == 5) d = (d + 1) % 31;
    setTime(h, m, s, d, mt, y);
    break;
  }
  lastButton = 0;
}

/**
 * The preferences setup state where for instance backlight and contrast can be set.
 */
void state_preferences_setup() {
  dogm.start();
  do {
    CursorTo(0,0);
    PrintString(STRING_BACK, preferences_setup_pos == 0);
    CursorTo(0,1);
    PrintString(STRING_BACKLIGHT, preferences_setup_pos == 1 && preferenecs_setup_edit == 0);
    PrintChar(':');
    PrintNumber(backlight, preferences_setup_pos == 1 && preferenecs_setup_edit == 1);
    CursorTo(0,2);
    PrintString(STRING_CONTRAST, preferences_setup_pos == 2 && preferenecs_setup_edit == 0);
    PrintChar(':');
    PrintNumber(contrast, preferences_setup_pos == 2 && preferenecs_setup_edit == 1);
    CursorTo(0,3);
    PrintString(STRING_BREAK_TEMP, preferences_setup_pos == 3 && preferenecs_setup_edit == 0);
    PrintChar(':');
    PrintNumber(breakTemp, preferences_setup_pos == 3 && preferenecs_setup_edit == 1);
    CursorTo(0,4);
    PrintString(STRING_SOUND, preferences_setup_pos == 4 && preferenecs_setup_edit == 0);
    PrintChar(':');
    PrintNumber(sound, preferences_setup_pos == 4 && preferenecs_setup_edit == 1);
  } while(dogm.next());

  switch(lastButton) {
  case BUTTON_MENU:
    if(preferences_setup_pos == 0) {
      goto_state(STATE_MENU);
    } else {
      if(preferenecs_setup_edit == 1) {
        if(preferences_setup_pos == 1) {
          updateEeprom(EEPROM_BACKLIGHT_OFFSET, backlight);
        } else if(preferences_setup_pos == 2) {
          updateEeprom(EEPROM_CONTRAST_OFFSET, contrast);
        } else if(preferences_setup_pos == 3) {
          updateEeprom(EEPROM_BREAK_TEMP, breakTemp);
        } else if(preferences_setup_pos == 4) {
          updateEeprom(EEPROM_SOUND_EVENT_OFFSET, sound);
        }
        preferenecs_setup_edit = 0;
      } else {
        preferenecs_setup_edit = 1;
      }
    }
    break;
  case BUTTON_UP:
    if(preferenecs_setup_edit == 0) {
      if(preferences_setup_pos > 0) preferences_setup_pos -= 1;
    } else {
      if(preferences_setup_pos == 1) {
        backlight = max(BACKLIGHT_STEPS, backlight - BACKLIGHT_STEPS);
        requestedBacklight = backlight;
      } else if(preferences_setup_pos == 2) {
        contrast = max(MIN_CONTRAST, contrast - 1);
        dogm.setContrast(contrast);
      } else if(preferences_setup_pos == 3) {
        breakTemp = max(0, breakTemp - 1);
      } else if(preferences_setup_pos == 4) {
        sound = 0;
      } else {
        preferenecs_setup_edit = 0;
      }
    }
    break;
  case BUTTON_DOWN:
    if(preferenecs_setup_edit == 0) {
      if(preferences_setup_pos < 4) preferences_setup_pos += 1;
    } else {
      if(preferences_setup_pos == 1) {
        backlight = min(256 - BACKLIGHT_STEPS, backlight + BACKLIGHT_STEPS);
        requestedBacklight = backlight;
        updateEeprom(EEPROM_BACKLIGHT_OFFSET, backlight);
      } else if(preferences_setup_pos == 2) {
        contrast = min(MAX_CONTRAST, contrast + 1);
        dogm.setContrast(contrast);
        updateEeprom(EEPROM_CONTRAST_OFFSET, contrast);
      } else if(preferences_setup_pos == 3) {
        breakTemp = min(100, breakTemp + 1);
      } else if(preferences_setup_pos == 4) {
        sound = 1;
      } else {
        preferenecs_setup_edit = 0;
      }
    }
    break;
  }
  lastButton = 0;

}

/**
 * The table view state where temperatures are displayed with text.
 */
void state_table_view() {

  dogm.start();
  do {
    for(byte i=0; i<TABLE_VIEW_TABLE.rows(); i++) {
      byte id = TABLE_VIEW_TABLE[i][0];
      CursorTo(TABLE_VIEW_TABLE[i][2]*10, TABLE_VIEW_TABLE[i][1]);
      if(id >= VARIABLE_ID_START) {
        PrintString((char*)pgm_read_word(&(VARIABLE_STRING_TABLE[id - VARIABLE_ID_START])));
        PrintChar(':');
        PrintNumber(lastVariable[id - VARIABLE_ID_START], 0, 0, 1);
      } else {
        PrintString((char*)pgm_read_word(&(DEVICE_STRING_TABLE[id])));
        PrintChar(':');
        if(lastTemp[id] == ILLEGAL_TEMP) {
          dogm.print('-'); dogm.print('-');
        } else {
          PrintNumber(lastTemp[id], 0, 0, 1);
        }
      }
    }
  } while(dogm.next());

  check_events();

  if(currentBacklight == requestedBacklight) {
    switch(lastButton) {
    case BUTTON_MENU:
      goto_state(STATE_MENU);
      break;
    case BUTTON_UP:
      goto_state(STATE_MAIN);
      break;
    case BUTTON_DOWN:
      history_pos = 0;
      goto_state(STATE_HISTORY_VIEW);
      break;
    }
  }
  lastButton = 0;
}

/**
 * The history view where the variable history are displayed.
 */
void state_history_view() {

  dogm.start();
  do {
    CursorTo(0,0);
    byte id = VARIABLE_DEVICE_TABLE[historySettings[history_pos].data];
    char* titleString;
    if(id >= VARIABLE_ID_START) {
      titleString = (char*)pgm_read_word(&(VARIABLE_STRING_TABLE[id - VARIABLE_ID_START]));
    } else {
      titleString = (char*)pgm_read_word(&(DEVICE_STRING_TABLE[id]));
    }
    PrintString(titleString);

    int cur = lastHistoryPos[history_pos];
    int offset = history_pos*128;

    dogm.setHLine(100, 127, 1);
    dogm.setHLine(100, 127, 61);
    dogm.setHLine(100, 127, 30);
    dogm.setXY(100-CHAR_WIDTH*3, 1);
    dogm.print((int)historySettings[history_pos].min);
    dogm.setXY(100-CHAR_WIDTH*3, 30-CHAR_HEIGHT/2);
    dogm.print((int)historySettings[history_pos].min+(historySettings[history_pos].max-historySettings[history_pos].min)/2);
    dogm.setXY(100-CHAR_WIDTH*3, 62-CHAR_HEIGHT);
    dogm.print((int)historySettings[history_pos].max);

    for(word i=1; i<127; i++) {
      byte value1 = history[offset + ((i+cur-1) % 128)];
      byte value2 = history[offset + ((i+cur)   % 128)];
      dogm.drawLine(i-1, value1, i, value2);
    }

  } while(dogm.next());

  check_events();

  if(currentBacklight == requestedBacklight) {
    switch(lastButton) {
    case BUTTON_MENU:
      goto_state(STATE_MENU);
      break;
    case BUTTON_UP:
      if(history_pos == 0) {
        goto_state(STATE_TABLE_VIEW);
      } else if(history_pos > 0) {
        history_pos -= 1;
      }
      break;
    case BUTTON_DOWN:
      if(history_pos >= (NUMBER_OF_HISTORY-1)) {
        goto_state(STATE_MAIN);
      } else if(history_pos < NUMBER_OF_HISTORY-1) {
        history_pos += 1;
      }
      break;
    }
  }
  lastButton = 0;

}

/**
 * The history setup state where data, resolution and ranges is set.
 */
void state_history_setup() {
  dogm.start();
  do {
    CursorTo(0,0);
    PrintString(STRING_BACK, history_setup_pos == 0);
    for(byte i=0;i<NUMBER_OF_HISTORY;i++) {
      char* dataString;
      byte data = VARIABLE_DEVICE_TABLE[historySettings[i].data];
      if(data < VARIABLE_ID_START) {
        dataString = (char*)pgm_read_word(&(DEVICE_STRING_TABLE[data]));
      } else if(data - VARIABLE_ID_START < NUMBER_OF_VARIABLES) {
        dataString = (char*)pgm_read_word(&(VARIABLE_STRING_TABLE[data- VARIABLE_ID_START]));
      } else {
        dataString = (char*)STRING_DASHES;
      }

      bool lineSelected = history_setup_pos == (1 + i);
      CursorTo(0,1+i);
      PrintNumber(i, lineSelected && history_setup_edit == 0);
      PrintChar(':', lineSelected && history_setup_edit == 0);
      PrintString(dataString, lineSelected && (history_setup_edit == 0 || history_setup_edit == 1));
      PrintChar(' ', lineSelected && history_setup_edit == 0);
      PrintNumber(historySettings[i].resolution, lineSelected && (history_setup_edit == 0 || history_setup_edit == 2));
      PrintChar('m', lineSelected && history_setup_edit == 0);
      PrintChar(' ', lineSelected && history_setup_edit == 0);
      PrintNumber(historySettings[i].min, lineSelected && (history_setup_edit == 0 || history_setup_edit == 3), 0, 1);
      PrintChar('-', lineSelected && history_setup_edit == 0);
      PrintNumber(historySettings[i].max, lineSelected && (history_setup_edit == 0 || history_setup_edit == 4), 0, 1);
    }
  } while(dogm.next());

  switch(lastButton) {
  case BUTTON_MENU:
    if(history_setup_pos == 0) {
      goto_state(STATE_MENU);
    } else {
      int offset = EEPROM_HISTORY_OFFSET + (history_setup_pos - 1)*HISTORY_RESERVED_EEPROM;
      switch(history_setup_edit) {
      case 1:
        updateEeprom(offset + HISTORY_DATA_OFFSET, historySettings[history_setup_pos - 1].data);
        break;
      case 2:
        updateEeprom(offset + HISTORY_RESOLUTION_OFFSET, historySettings[history_setup_pos - 1].resolution);
        break;
      case 3:
        updateEeprom(offset + HISTORY_MIN_VALUE_OFFSET, (byte) (0xff & historySettings[history_setup_pos - 1].min));
        break;
      case 4:
        updateEeprom(offset + HISTORY_MAX_VALUE_OFFSET, (byte) (0xff & historySettings[history_setup_pos - 1].max));
        break;
      }

      history_setup_edit = (history_setup_edit + 1) % 5;
    }
    break;
  case BUTTON_UP:
    switch(history_setup_edit) {
    case 0:
      if(history_setup_pos > 0) history_setup_pos -= 1;
      break;
    case 1:
      historySettings[history_setup_pos - 1].data = (historySettings[history_setup_pos - 1].data + 1) % VARIABLE_DEVICE_TABLE.count();
      break;
    case 2:
      historySettings[history_setup_pos - 1].resolution += 1;
      break;
    case 3:
      historySettings[history_setup_pos - 1].min += 1;
      break;
    case 4:
      historySettings[history_setup_pos - 1].max += 1;
      break;
    default:
      history_setup_edit = 0;
    }
    break;
  case BUTTON_DOWN:
    switch(history_setup_edit) {
    case 0:
      if(history_setup_pos < NUMBER_OF_HISTORY) history_setup_pos += 1;
      break;
    case 1:
      historySettings[history_setup_pos - 1].data = (historySettings[history_setup_pos - 1].data - 1) % VARIABLE_DEVICE_TABLE.count();
      break;
    case 2:
      historySettings[history_setup_pos - 1].resolution -= 1;
      break;
    case 3:
      historySettings[history_setup_pos - 1].min -= 1;
      break;
    case 4:
      historySettings[history_setup_pos - 1].max -= 1;
      break;
    default:
      history_setup_edit = 0;
    }
    break;
  }
  lastButton = 0;

}

/**
 * Reads data from the sensors for calculation and store in lastTemp and lastVariable.
 */
void read_data() {
  bool lastRequestDone = tempSensors.lastRequestDone();

  byte sensor[8];
  if(lastRequestDone) {
    for(byte i=0; i<NUMBER_OF_DEVICES; i++) {
      byte exists = get_device_sensor(i, sensor);
      if(exists == 1) {
        float tempTemp = tempSensors.getTempC(sensor);;
        if(TEST_MODE) tempTemp = (float) random(20, 90);
        if(TEST_MODE && i == DEVICE_UTE) tempTemp = ((float) random(0, 40)) -  20.0;

        if(i <= DEVICE_TANK7 and i >= DEVICE_TANK1 and
            tempTemp <= breakTemp and lastTemp[i] > breakTemp and lastMinDevice != i) {
          if(lastMinDevice > i) {
            tankEmptyTime = now() + (now() - lastMinTempTime) * (i - DEVICE_TANK1);
          } else {
            tankEmptyTime = 0;
          }
          lastMinTempTime = now();
          lastMinDevice = i;
        }
        lastTemp[i] = tempTemp;
      } else {
        lastTemp[i] = ILLEGAL_TEMP;
      }
    }
    tempSensors.requestTemperatures(true);
  }

  if(fireState == true) {
    float effect = (lastTemp[DEVICE_LADDNING_UT] -
        lastTemp[DEVICE_LADDNING_RETUR]) * 2500 * 1.16 / 1000;
    lastVariable[VARIABLE_EFFECT] = (byte) round(effect);
  } else {
    lastVariable[VARIABLE_EFFECT] = 0;
  }

  float x = breakTemp;
  float load = 0;
  for(byte i=DEVICE_TANK1; i<DEVICE_TANK7; i++) {
    if(lastTemp[i] >= x && lastTemp[i+1] <= x) {
      float y0 = i - DEVICE_TANK1;
      float y1 = i - DEVICE_TANK1 + 1;
      float x0 = lastTemp[i];
      float x1 = lastTemp[i+1];
      if(x0 != x1) {
        load = y0 + (x-x0)*(y1-y0)/(x1-x0);
      } else {
        load = y0;
      }
    }
  }
  if(lastTemp[DEVICE_TANK7] > x) {
    load = DEVICE_TANK7 - DEVICE_TANK1;
  }
  lastVariable[VARIABLE_LOAD] = load*100.0/6.0;

  //TODO: HERE TRY HERMITE SPLINE???

  if((lastLoadHistory + ((time_t)LOAD_HISTORY_RESOLUTION)*60) < now()) {
    for(byte i=NUMBER_OF_LOAD_HISTORY-1;i>0;i--) {
      loadHistory[i] = loadHistory[i-1];
//      Serial.flush();
//      Serial.println(loadHistory[i]);
    }
    loadHistory[0] = lastVariable[VARIABLE_LOAD];
//    Serial.flush();
//    Serial.println(loadHistory[0]);
//    Serial.println('-');
//    Serial.flush();
    lastLoadHistory = now();

    // Linear regression
    float xAvg = 0;
    float yAvg = 0;
    for(byte i=0; i<NUMBER_OF_LOAD_HISTORY; i++) {
      xAvg += (float)i*(float)LOAD_HISTORY_RESOLUTION;
      yAvg += loadHistory[i];
    }
    xAvg = xAvg / NUMBER_OF_LOAD_HISTORY;
    yAvg = yAvg / NUMBER_OF_LOAD_HISTORY;

    float v1 = 0;
    float v2 = 0;
    for(byte i=0; i<NUMBER_OF_LOAD_HISTORY; i++) {
      v1 += ((float)i - xAvg) * (loadHistory[i] - yAvg);
      v2 += pow((float)i - xAvg, 2);
    }

    float a;
    if(v2 > 0 || v2 < 0) {
      a = v1/v2;
    } else {
      a = 1000;
    }
    float b = yAvg - a * xAvg;
    float e;
    if(a > 0) {
      e = -(100.0-b) / a;
    } else if(a < 0){
      e = (-b / a);
    } else {
      e = 0;
    }

    lastVariable[VARIABLE_TIME_EMPTY] = e;

//    Serial.flush();
//    Serial.print("a:");
//    Serial.println(a);
//    Serial.flush();
//    Serial.print("b:");
//    Serial.println(b);
//    Serial.flush();
//    Serial.print("e:");
//    Serial.println(e);
  }

  for(byte i=0; i<NUMBER_OF_HISTORY; i++) {
    if(lastHistoryTime[i]+((time_t)historySettings[i].resolution)*60 < now()) {
      float value;
      byte id = VARIABLE_DEVICE_TABLE[historySettings[i].data];
      if(id >= VARIABLE_ID_START) {
        value = lastVariable[id - VARIABLE_ID_START];
      } else {
        value = lastTemp[id];
      }
      word idx = i*128+lastHistoryPos[i];
      history[idx] = (byte) map(value*100.0, historySettings[i].min*100.0, historySettings[i].max*100.0, 1, 61);
      lastHistoryPos[i] = (lastHistoryPos[i]+1) % 128;
      lastHistoryTime[i] = now();
    }
  }
}

/**
 * Setup the program. Called before loop().
 */
void setup() {
  backlight = EEPROM.read(EEPROM_BACKLIGHT_OFFSET);
  requestedBacklight = backlight;
  contrast = EEPROM.read(EEPROM_CONTRAST_OFFSET);
  contrast = max(MIN_CONTRAST,min(MAX_CONTRAST,contrast));
  breakTemp = EEPROM.read(EEPROM_BREAK_TEMP);
  sound = EEPROM.read(EEPROM_SOUND_EVENT_OFFSET);

  for(byte i=0;i<NUMBER_OF_HISTORY;i++) {
    int offset = EEPROM_HISTORY_OFFSET+i*HISTORY_RESERVED_EEPROM;
    historySettings[i].data       = EEPROM.read(offset+HISTORY_DATA_OFFSET)  % VARIABLE_DEVICE_TABLE.count();
    historySettings[i].resolution = EEPROM.read(offset+HISTORY_RESOLUTION_OFFSET);
    historySettings[i].max        = (char) (0xff & EEPROM.read(offset+HISTORY_MAX_VALUE_OFFSET));
    historySettings[i].min        = (char) (0xff & EEPROM.read(offset+HISTORY_MIN_VALUE_OFFSET));
  }

  pinMode(BACKLIGHT_PIN,OUTPUT);
  digitalWrite(BACKLIGHT_PIN,LOW);
  pinMode(RESET_PIN,OUTPUT);
  digitalWrite(RESET_PIN,HIGH);

  pinMode(BUZZER_PIN,OUTPUT);
  digitalWrite(BUZZER_PIN,LOW);

  dogm.setInvertPixelMode(0);
  dogm.setFont(font_6x10);
  dogm.start();

  setBacklight(currentBacklight, true);
  dogm.setContrast(contrast);

//  Serial.begin(9600);
//  Serial.println("Start");

  AnalogButton.begin(0, 3, buttonValues, &buttonReceived);

  tempSensors.begin();
  tempSensors.requestTemperatures(true);

  goto_main();

  //int free_memory = get_free_memory();
  //Serial.println(free_memory, (int) DEC);

  //setTime(10, 49, 00, 20, 2, 2011);

  lastMinTempTime = now();
}

/**
 * The main loop. Called indefinitely.
 */
void  loop() {
  if(requestedBacklight < currentBacklight) {
    currentBacklight = max(currentBacklight - FADE_SPEED,
        requestedBacklight);
    setBacklight(currentBacklight, true);
    delay(FADE_DELAY);
  } else if(requestedBacklight > currentBacklight) {
    currentBacklight = min(currentBacklight + FADE_SPEED,
        requestedBacklight);
    setBacklight(currentBacklight, true);
    delay(FADE_DELAY);
  } else {
    //dogm.showLibInfo();
    read_data();
    switch(state) {
      case STATE_MENU:
        state_menu();
        break;
      case STATE_TABLE_VIEW:
        state_table_view();
        break;
      case STATE_HISTORY_VIEW:
        state_history_view();
        break;
      case STATE_HISTORY_SETUP:
        state_history_setup();
        break;
      case STATE_PREFERENCES_SETUP:
        state_preferences_setup();
        break;
      case STATE_TIME_SET:
        state_time_set();
        break;
      case STATE_DEVICE_SETUP:
        state_device_setup();
        break;
      case STATE_SENSOR_SELECT:
        state_sensor_select();
        break;
      case STATE_MAIN:
      default:
        state_main();
        break;
    }
  }
}

